#include "kiva/dsl.h"

#include <glog/logging.h>

#define KIVA_INSTANTIATE_TEMPLATE(Class, Enum, Name) \
  template class Class<NodeType::Enum>;

namespace kiva {

template class ExprAPI<DslNode>;
template class ExprAPI<NameDslNode>;

DslNode::DslNode(InitializerList items) : DslNode(NewFromInitializer(items)) {}
DslNode DslNode::operator=(const DslNode& rhs) const {
  return NewAssign(rhs);
}

NodeRef DslNode::NewFromInitializer(InitializerList items) {
  NodeRef node = Node::NewWithChildren(NodeType::kTuple);
  for (const DslNode& item : items)
    node->AddChild(item);
  return node;
}

DslNodeBuilder::operator DslNode() {
  return node();
}

void DslNodeBuilder::AddChildren(DslNode::InitializerList nodes) {
  for (auto node : nodes) {
    NodeBuilder::AddChild(node);
  }
}

void DslNodeBuilder::AddChild(DslNode node) {
  NodeBuilder::AddChild(node);
}

DslNode DslNodeBuilder::WithType(DslNode node, NodeType new_type) {
  NodeRef(node)->set_type(new_type);
  return node;
}

DslNode DslNodeBuilder::MakeBody(DslNode node, NodeType type) {
  NodeRef ref = node;
  if (ref->type() == NodeType::kTuple) {
    ref->set_type(type);
  }
  return node;
}

namespace dsl {

template <NodeType Type>
FunctionDefBuilder<Type>::FunctionDefBuilder(const char* name, DslNode body)
    : FunctionDefBuilder() {
  AddChildren({Node::NewName(name), MakeBody(body)});
}

// def(name, arguments, returns, body)
template <NodeType Type>
FunctionDefBuilder<Type>::FunctionDefBuilder(const char* name,
                                             DslNode arguments,
                                             DslNode returns,
                                             DslNode body)
    : FunctionDefBuilder() {
  AddChildren({
      Node::NewName(name),
      WithType(arguments, NodeType::kFormalArguments),
      returns,
      MakeBody(body),
  });
}

// def(name, arguments, body)
template <NodeType Type>
FunctionDefBuilder<Type>::FunctionDefBuilder(const char* name,
                                             DslNode arguments,
                                             DslNode body)
    : FunctionDefBuilder() {
  AddChildren({
      Node::NewName(name),
      WithType(arguments, NodeType::kFormalArguments),
      MakeBody(body),
  });
}

class_::class_(const char* name, DslNode body) : class_() {
  AddChildren({Node::NewName(name), MakeBody(body)});
}
class_::class_(const char* name, DslNode bases, DslNode body) : class_() {
  AddChildren({Node::NewName(name), bases, MakeBody(body)});
}

DslNode RaiseBuilder::operator()(DslNode exc) const {
  return Node::NewWithChildren(NodeType::kRaise, {exc});
}
DslNode RaiseBuilder::operator()(DslNode exc, DslNode from) const {
  return Node::NewWithChildren(NodeType::kRaise, {exc, from});
}

except::except(DslNode::InitializerList body) : except() {
  AddChild(MakeBody(body));
}
except::except(DslNode exc, DslNode body) : except() {
  AddChildren({exc, MakeBody(body)});
}
except::except(DslNode exc, const char* asname, DslNode body) : except() {
  AddChildren({exc, Node::NewName(asname), MakeBody(body)});
}

assert_::assert_(DslNode predicate) : assert_() {
  AddChild(predicate);
}
assert_::assert_(DslNode predicate, DslNode msg) : assert_() {
  AddChildren({predicate, msg});
}

template <NodeType Type>
NamesBuilder<Type>::NamesBuilder(DslNode::InitializerList names)
    : DslNodeBuilder(Type) {
  AddChildren(names);
}

template <NodeType Type>
ValueBuilder<Type>::ValueBuilder(DslNode value) : DslNodeBuilder(Type) {
  AddChild(value);
}

from::from(const char* module) : DslNodeBuilder(NodeType::kImportFrom) {
  AddChild(Node::NewName(module));
}
DslNode from::import(DslNode names) {
  AddChild(names);
  return *this;
}

lambda::lambda(DslNode args, DslNode expr) : DslNodeBuilder(NodeType::kLambda) {
  AddChildren({args, expr});
}

// Instantiate all builder templates.
KIVA_FUNCTION_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_OPTIONAL_VALUE_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_FOR_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_CONDITION_BODY_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_BODY_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_WITH_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_NAMES_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_ODDBALL_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)
KIVA_VALUE_BUILDERS(KIVA_INSTANTIATE_TEMPLATE)

NameDslNode operator""_n(const char* str, std::size_t len) {
  DCHECK(str);
  return NameDslNode(std::string_view(str, len));
}

}  // namespace dsl
}  // namespace kiva